package com.sm2cppa.utils;

import com.sm2cppa.contract.MapPkToTx;
import com.sm2cppa.entity.UserBody;
import org.apache.commons.lang3.tuple.Pair;
import org.fisco.bcos.sdk.client.Client;
import org.fisco.bcos.sdk.client.protocol.response.BcosTransaction;
import org.fisco.bcos.sdk.crypto.keypair.CryptoKeyPair;
import org.fisco.bcos.sdk.crypto.signature.SM2Signature;
import org.fisco.bcos.sdk.crypto.signature.SignatureResult;
import org.fisco.bcos.sdk.model.TransactionReceipt;
import org.fisco.bcos.sdk.transaction.builder.TransactionBuilderInterface;
import org.fisco.bcos.sdk.transaction.builder.TransactionBuilderService;
import org.fisco.bcos.sdk.transaction.codec.encode.TransactionEncoderInterface;
import org.fisco.bcos.sdk.transaction.codec.encode.TransactionEncoderService;
import org.fisco.bcos.sdk.transaction.manager.TransactionProcessorFactory;
import org.fisco.bcos.sdk.transaction.model.gas.DefaultGasProvider;
import org.fisco.bcos.sdk.transaction.model.po.RawTransaction;
import org.fisco.bcos.sdk.transaction.pusher.TransactionPusherInterface;
import org.fisco.bcos.sdk.transaction.pusher.TransactionPusherService;
import org.fisco.bcos.sdk.utils.Hex;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.math.BigInteger;
import java.util.List;

@Component
public class Transaction {

    @Autowired
    private CertUtils certUtils;
    @Value("${caaddr}")
    private String ca_addr;
    @Value("${user1addr}")
    private String user1_addr;
    @Value("${user2addr}")
    private String user2_addr;


    /**
     * 根据交易内容txData构造RawTransaction，并指定交易发送的地址为to(部署合约时，to为全0的合约地址)
     * @param transactionBuilder 交易构造对象
     * @param client Client对象，用于获取BlockLimit
     * @param to 交易发送的目标地址(部署合约时，to为全0的合约地址)
     * @param txData 编码后的交易
     * @return 创建的RawTransaction
     */
    public RawTransaction createRawTransaction(TransactionBuilderInterface transactionBuilder, Client client, String to, String txData)
    {
        // 获取chainId和groupId
        Pair<String, Integer> chainIdAndGroupId = TransactionProcessorFactory.getChainIdAndGroupId(client);
        return transactionBuilder.createTransaction(
                DefaultGasProvider.GAS_PRICE,
                DefaultGasProvider.GAS_LIMIT,
                to,
                txData,
                BigInteger.ZERO,
                new BigInteger(chainIdAndGroupId.getLeft()),
                BigInteger.valueOf(chainIdAndGroupId.getRight()),
                "");
    }


    /**
     * 对RawTransaction进行RLP编码，返回编码内容的哈希
     *
     * @param transactionEncoder 交易编码器
     * @param client client对象，用于确定使用的哈希算法类型
     * @param rawTransaction 需要编码的交易
     * @return 编码后交易的哈希
     */
    public byte[] encodeRawTransactionAndGetHash(
            TransactionEncoderInterface transactionEncoder,
            Client client,
            RawTransaction rawTransaction) {
        byte[] encodedTransaction = transactionEncoder.encode(rawTransaction, null);
        return client.getCryptoSuite().hash(encodedTransaction);
    }

    /**
     * 对编码后的交易哈希进行SM2签名
     * @param dataToSign 编码后交易的哈希
     */
    public SignatureResult SignTranHash(byte[] dataToSign,Client client){
        CryptoKeyPair cryptoKeyPair = client.getCryptoSuite().getCryptoKeyPair();
        SM2Signature sm2=new SM2Signature();
        SignatureResult sign=sm2.sign(dataToSign,cryptoKeyPair);
        return  sign;

    }


    /**
     * 根据RawTransaction和签名结果产生带有签名的交易
     *
     * @param transactionEncoder 交易编码器
     * @param transaction 不带有签名的交易
     * @param signatureResult 签名服务器返回的反序列化的签名结果
     * @return 带有签名的交易编码
     */
    public byte[] createSignedTransaction(
            TransactionEncoderInterface transactionEncoder,
            RawTransaction transaction,
            SignatureResult signatureResult) {
        return transactionEncoder.encode(transaction, signatureResult);
    }


    /**
     * 发送带有签名的交易
     *
     * @param txPusher 交易发送器
     * @param signedTransaction 带有签名的交易
     * @return 交易回执
     */
    TransactionReceipt sendTransaction(
            TransactionPusherInterface txPusher, byte[] signedTransaction) {
        return txPusher.push(Hex.toHexString(signedTransaction));
    }



    /**
     * 产生有签名的交易
     */
    public byte[] makeSignTransaction(Client client, String to, String txData){
        TransactionBuilderInterface transactionBuilder = new TransactionBuilderService(client);
        TransactionEncoderInterface transactionEncoder = new TransactionEncoderService(client.getCryptoSuite());
        RawTransaction rawTransaction=createRawTransaction(transactionBuilder, client, to, txData);
        byte[] dataToSign=encodeRawTransactionAndGetHash(transactionEncoder,client,rawTransaction);
        SignatureResult signatureResult=SignTranHash(dataToSign,client);
        return createSignedTransaction(transactionEncoder,rawTransaction,signatureResult);
    }

    /**
     * 产生和发送带有签名的交易
     * @param client 发送方客户端
     * @param to 目标账户地址
     * @param txData 交易input
     * @return 交易回执
     */
    public TransactionReceipt makeandsendTransaction(Client client, String to, String txData){

        long startTime2 = System.nanoTime();
        byte[] signedTransaction=makeSignTransaction(client, to, txData);
        long endTime2 =  System.nanoTime();
        double duration2 = (endTime2 - startTime2) / 1_000_000.0;
        System.out.println("makeSignTransaction time: "+duration2+" ms");

        TransactionPusherInterface txPusher = new TransactionPusherService(client);

        long startTime = System.nanoTime();
        TransactionReceipt transactionReceipt=sendTransaction(txPusher,signedTransaction);
        long endTime =  System.nanoTime();
        double duration = (endTime - startTime) / 1_000_000.0;
        System.out.println("sendTransaction time: "+duration+" ms");

//        byte[] signedTransaction=makeSignTransaction(client, to, txData);
//        TransactionPusherInterface txPusher = new TransactionPusherService(client);
//        TransactionReceipt transactionReceipt=sendTransaction(txPusher,signedTransaction);
        return transactionReceipt;
    }

    /**
     * 将公钥生成公钥证书，嵌入到交易中发布到区块链上，更新合约内容
     * @param publicKey  SM2公钥
     * @param map_addr  合约地址
     * @param CAclient CA客户端
     * @return 交易哈希
     */
    public String Register_PublicKey(byte[] publicKey, String map_addr, Client CAclient)throws Exception{
        String useraddr=user1_addr;
        CryptoKeyPair cryptoKeyPair = CAclient.getCryptoSuite().getCryptoKeyPair();

        //生成公钥证书
        String pkCert= certUtils.makeCert(publicKey);


        //将公钥证书嵌入到交易中发送到区块链上
        long startTime = System.nanoTime();
        TransactionReceipt transactionReceipt= makeandsendTransaction(CAclient,useraddr,pkCert);
        long endTime =  System.nanoTime();
        double duration = (endTime - startTime) / 1_000_000.0;
        System.out.println("makeandsendTransaction time: "+duration+" ms");

        String txid=transactionReceipt.getTransactionHash();

       //用SM3计算pkhash
        byte[] pkhash= KeyDerive.Sm3Hash(publicKey);


        long startTime2 = System.nanoTime();
        MapPkToTx mapPkToTx=MapPkToTx.load(map_addr,CAclient,cryptoKeyPair);
        long endTime2 =  System.nanoTime();
        double duration2 = (endTime2 - startTime2) / 1_000_000.0;
        System.out.println("mapPkToTx.load time: "+duration2+" ms");

        //调用合约的update方法
        long startTime3 = System.nanoTime();
        mapPkToTx.update(pkhash,txid);
        long endTime3 = System.nanoTime();
        double duration3 = (endTime3 - startTime3) / 1_000_000.0;
        System.out.println("mapPkToTx.update time: "+duration3+" ms");

        return txid;
    }


    /**
     * 根据交易哈希查询公钥对应的交易，从交易中获取公钥证书返回
     * @param TxID  交易哈希
     * @param userClient  用户客户端
     * @return 公钥证书
     */
    public  String Get_PublicKey(String TxID,Client userClient)throws Exception{
        long startTime3 = System.nanoTime();
        BcosTransaction tr=userClient.getTransactionByHash(TxID);
        long endTime3 =  System.nanoTime();
        double duration3 = (endTime3 - startTime3) / 1_000_000.0;
        System.out.println("getTransactionByHash time: "+duration3+" ms");

        //从交易获取证书
        String pkCert=tr.getResult().getInput();
        return pkCert;
    }

    /**
     * 撤销公钥
     * @param publicKey  SM2公钥
     * @param map_addr  合约地址
     * @param CAclient  CA客户端
     * @return 布尔类型表示撤销是否成功
     */
    //只有当client不是CAclient的时候才会返回flase，当pkhash键不存在map中会将其值设为默认值“”也会返回true
    public boolean Revoke_PublicKey(byte[] publicKey, String map_addr, Client CAclient)throws Exception{
        CryptoKeyPair cryptoKeyPair = CAclient.getCryptoSuite().getCryptoKeyPair();
        MapPkToTx mapPkToTx=MapPkToTx.load(map_addr,CAclient,cryptoKeyPair);
        byte[] pkhash=KeyDerive.Sm3Hash(publicKey);
        TransactionReceipt tr=mapPkToTx.deletetx(pkhash);
        return tr.isStatusOK();
    }

    /**
     * 撤销用户
     * @param user  用户
     * @param map_addr  合约地址
     * @param CAclient  CA客户端
     */
    public void Revoke_user(UserBody user, String map_addr, Client CAclient)throws Exception{
        CryptoKeyPair cryptoKeyPair = CAclient.getCryptoSuite().getCryptoKeyPair();
        MapPkToTx mapPkToTx=MapPkToTx.load(map_addr,CAclient,cryptoKeyPair);
       for(byte[] pkhash:user.getPkhashList()){
           mapPkToTx.deletetx(pkhash);
       }
    }

    /**
     * 批量注册公钥，即进行batch次公钥派生，并将每次派生的公钥进行注册
     * @param publicKey  SM2公钥
     * @param chain  链码
     * @param keyIndex  密钥派生索引
     * @param batch  公钥派生次数，也是注册公钥数量
     * @param map_addr  合约地址
     * @param CAclient CA客户端
     * @param user 存储用户信息的容器
     * @return 更新的用户对象
     */
    public UserBody BatchRegisterPk(byte[] publicKey, byte[] chain, int keyIndex, int batch, String map_addr, Client CAclient, UserBody user)throws Exception{

        String txid="";
        for(int i=0;i<batch;i++){
            byte[][] up= KeyDerive.SM2_PublicKeyDerive(publicKey,keyIndex,chain);
            publicKey=up[0];
            chain=up[1];
            keyIndex=keyIndex+1;
            user.addpkhash(KeyDerive.Sm3Hash(publicKey));
            txid=Register_PublicKey(publicKey,map_addr,CAclient);
        }

        //放入派生公钥链尾端公钥（最新公钥）和最新链码
        user.setChain(Hex.toHexString(chain));
        user.setKey(Hex.toHexString(publicKey));
        user.setKeyIndex(keyIndex);
        return user;
    }

}
